package com.starlink.service.impl;

import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import cn.hutool.core.convert.Convert;
import com.github.pagehelper.PageInfo;
import com.github.pagehelper.PageHelper;
import com.starlink.common.annotation.client.WeixinJsClient;
import com.starlink.common.annotation.client.base.RefundAmount;
import com.starlink.common.annotation.client.weixin.*;
import com.starlink.common.config.WechatConfigurationConfig;
import com.starlink.common.enums.ErrorCode;
import com.starlink.common.exception.BusinessException;
import com.starlink.model.domain.SchoolbagOrderDO;
import com.starlink.model.request.mall.WechatPay;
import com.starlink.model.request.mall.WechatRefund;
import com.starlink.model.vo.mall.WeixinRefundOrderVO;
import com.starlink.service.SchoolbagOrderService;
import com.starlink.utils.IRedisCacheService;
import com.starlink.utils.MoneyUtil;
import com.starlink.utils.OrderNumUtil;
import com.starlink.utils.WeixinUtil;
import com.wechat.pay.contrib.apache.httpclient.util.PemUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.domain.Pageable;
import com.starlink.common.utils.AssertUtil;
import com.starlink.model.dtomapper.DepositRecordDtoMapper;
import com.starlink.model.domain.DepositRecordDO;
import com.starlink.mapper.DepositRecordMapper;
import com.starlink.model.request.DepositRecordRequest;
import com.starlink.service.DepositRecordService;
import com.starlink.model.util.DepositRecordConvert;
import com.starlink.model.vo.DepositRecordVO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.ObjectUtils;

import static com.starlink.common.enums.DepositOpTypeEnum.DEPOSIT_ADD;
import static com.starlink.common.enums.DepositOpTypeEnum.DEPOSIT_SUB;
import static com.starlink.common.enums.DepositTitleEnum.CASH;
import static com.starlink.common.enums.DepositTitleEnum.DEPOSIT_CHARGE;
import static com.starlink.common.enums.OrderTypeEnum.DEPOSIT;
import static com.starlink.common.enums.RedisHashKeyEnum.ORDER_REFUND;
import static com.starlink.utils.PageUtils.getOrderBy;

import java.io.ByteArrayInputStream;
import java.io.UnsupportedEncodingException;
import java.math.BigDecimal;
import java.nio.charset.StandardCharsets;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.SignatureException;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Collectors;

/**
 * @author zhanghong
 */
@Slf4j
@Service
public class DepositRecordServiceImpl extends ServiceImpl<DepositRecordMapper, DepositRecordDO> implements DepositRecordService {
    @Autowired
    private DepositRecordMapper depositRecordMapper;
    @Autowired
    private WeixinJsClient weixinJsClient;
    @Autowired
    private SchoolbagOrderService schoolbagOrderService;
    @Autowired
    private IRedisCacheService redisCacheService;

    private static final long EXPIRE_SECONDS = 7 * 24 * 60 * 60; // 过期时间，单位：秒

    @Override
    public DepositRecordVO getByKey(long id) {
        DepositRecordVO depositRecordVO = DepositRecordDtoMapper.INSTANCE.do2VO(getById(id));
        return depositRecordVO;
    }

    @Override
    public PageInfo<DepositRecordVO> pageQuery(Pageable pageable,DepositRecordRequest request) {
        PageHelper.startPage(request.getPageNum(), request.getPageSize(), getOrderBy(pageable.getSort())).setReasonable(true);
        List<DepositRecordDO> list = depositRecordMapper.pageQuery(request);
        List<DepositRecordVO> depositRecordVOList = DepositRecordConvert.buildDepositRecordVoList(list);
        return new PageInfo(depositRecordVOList);
    }

    @Override
    public List<DepositRecordVO> listQuery(DepositRecordRequest request) {
        List<DepositRecordDO> list = depositRecordMapper.selectDOList(DepositRecordDtoMapper.INSTANCE.request2DO(request));
        List<DepositRecordVO> depositRecordVOS = DepositRecordConvert.buildDepositRecordVoList(list);
        return depositRecordVOS;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Boolean add(DepositRecordRequest request) {
        DepositRecordDO recordDO = DepositRecordDtoMapper.INSTANCE.request2DO(request);
        Long userId = recordDO.getUserId();
        Integer title = recordDO.getTitle();
        String payId = recordDO.getPayId();
        Integer operationType = recordDO.getOperationType();
        BigDecimal changeCashPledge = recordDO.getChangeCashPledge();
        BigDecimal balanceCashPledge = recordDO.getBalanceCashPledge();
        AssertUtil.assertNotNull(userId,"用户ID不能为空!");
        AssertUtil.assertNotNull(title,"标题不能为空!");
        AssertUtil.assertNotNull(payId,"微信订单ID不能为空!");
        AssertUtil.assertNotNull(operationType,"操作类型不能为空!");
        AssertUtil.assertNotNull(changeCashPledge,"变更金额不能为空!");
        AssertUtil.assertNotNull(balanceCashPledge,"剩余押金金额不能为空!");
        recordDO.setCreateTime(new Date());
        depositRecordMapper.insertDO(recordDO);
        return true;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Boolean updateByKey(DepositRecordRequest request) {
        AssertUtil.assertNotNull(request.getId(), "id不能为空");
        saveOrUpdate(DepositRecordDtoMapper.INSTANCE.request2DO(request));
        return true;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Boolean deleteDOByIds(String ids) {
        depositRecordMapper.deleteDOByIds(Convert.toStrArray(ids));
        return true;
    }

    @Override
    public UserPurchaseRes depositCharge(DepositRecordRequest request) {
        BigDecimal cashPledge = request.getChangeCashPledge();
        Long userId = request.getUserId();
        String openId = request.getOpenId();
        AssertUtil.assertNotNull(userId,"用户ID不能为空!");
        AssertUtil.assertNotNull(openId,"openId不能为空!");
        AssertUtil.assertNotNull(cashPledge,"变更金额不能为空!");
        AssertUtil.assertNotNull(BigDecimal.ZERO.equals(cashPledge),"金额不能为0!");
        AssertUtil.assertFalse(cashPledge.compareTo(BigDecimal.ZERO) < 0,"金额不能为负!");
        UserPurchaseRes vipRes = new UserPurchaseRes();
        //创建微信预订单
        String orderNum = "YJ"+OrderNumUtil.createUniqueOrderNum();
        WechatPay wechatPay = new WechatPay();
        wechatPay.setOrderType(DEPOSIT.getType());
        wechatPay.setAmount(cashPledge.toPlainString());
        wechatPay.setUserId(userId);
        String attach = JSONObject.toJSONString(wechatPay);
        WeixinJsCreateOrderReq req = new WeixinJsCreateOrderReq(orderNum, cashPledge.doubleValue(), attach, openId,"押金充值");
        try {
            WeixinJsCreateOrderRes res = weixinJsClient.createOrder(req);
            buildPayRes(vipRes, res.getPrepay_id());
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException("押金充值失败!");
        }
        return vipRes;
    }

    @Override
    public DepositRecordVO getDeposit(DepositRecordRequest request) {
        Long userId = request.getUserId();
        AssertUtil.assertNotNull(userId,"用户ID不能为空!");
        DepositRecordVO recordVO = new DepositRecordVO();
        DepositRecordDO recordDO = this.lambdaQuery()
                .eq(DepositRecordDO::getUserId, userId)
                .orderByDesc(DepositRecordDO::getCreateTime)
                .last("LIMIT 1")
                .one();
        if (recordDO==null){
            recordVO.setBalanceCashPledge(BigDecimal.ZERO);
        }else {
            recordVO.setBalanceCashPledge(recordDO.getBalanceCashPledge());
        }
        return recordVO;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Boolean depositRefund(DepositRecordRequest request) {
        Long userId = request.getUserId();
        AssertUtil.assertNotNull(userId,"userId不能为空!");
        List<SchoolbagOrderDO> list = schoolbagOrderService.lambdaQuery()
                .eq(SchoolbagOrderDO::getServiceFinish, 0)
                .eq(SchoolbagOrderDO::getUserId, userId)
                .list();
        AssertUtil.assertEmpty(list,"当前存在未完成书包订单,暂时无法退还押金!");
        List<DepositRecordDO> depositRecordDOS = this.lambdaQuery()
                .eq(DepositRecordDO::getUserId, userId)
                .list();
        AssertUtil.assertNotEmpty(depositRecordDOS,"当前用户不存在押金!");
        //只取最新一次退款后的数据
        DepositRecordDO orElse = depositRecordDOS.stream()
                .filter(item -> CASH.getType().equals(item.getTitle()))
                .sorted(Comparator.comparing(DepositRecordDO::getCreateTime).reversed())
                .findFirst()
                .orElse(null);
        List<DepositRecordDO> data = new ArrayList<>();
        if (!ObjectUtils.isEmpty(orElse)){
            Date createTime = orElse.getCreateTime();
            List<DepositRecordDO> filteredList = depositRecordDOS.stream()
                    .filter(obj -> obj.getCreateTime().after(createTime))
                    .collect(Collectors.toList());
            data.addAll(filteredList);
        }else {
            data.addAll(depositRecordDOS);
        }
        DepositRecordDO latest = data.stream()
                .max(Comparator.comparing(DepositRecordDO::getCreateTime)).get();
        BigDecimal balanceCashPledge = latest.getBalanceCashPledge();//剩余可用押金
        log.info("当前剩余退款押金->{}",balanceCashPledge.toPlainString());
        AssertUtil.assertFalse(balanceCashPledge.compareTo(BigDecimal.ZERO) <= 0,"当前没有可退押金!");
        List<Map<String, Object>> refundList = new ArrayList<>();
        AtomicBoolean atomicBoolean = new AtomicBoolean(true);
        AtomicReference<BigDecimal> reference = new AtomicReference<>(BigDecimal.ZERO);
        data.stream()
            .filter(item -> DEPOSIT_CHARGE.getType().equals(item.getTitle()))
            .sorted(Comparator.comparing(DepositRecordDO::getCreateTime).reversed())
            .forEach(item -> {
                BigDecimal changeCashPledge = item.getChangeCashPledge();//每次的充值金额
                if (changeCashPledge.compareTo(balanceCashPledge)>=0 && atomicBoolean.get()){
                    Map<String, Object> map = new HashMap<>();
                    map.put("payId",item.getPayId());
                    map.put("orderNum",item.getOrderNum());
                    map.put("total",changeCashPledge);
                    BigDecimal total = reference.get();
                    if (BigDecimal.ZERO.equals(total)){
                        map.put("refundPrice",balanceCashPledge);
                    }else {
                        map.put("refundPrice",balanceCashPledge.subtract(total));
                    }
                    refundList.add(map);
                    atomicBoolean.set(false);
                }
                if(changeCashPledge.compareTo(balanceCashPledge)<0 && atomicBoolean.get()){
                    Map<String, Object> map = new HashMap<>();
                    map.put("payId",item.getPayId());
                    map.put("orderNum",item.getOrderNum());
                    map.put("total",changeCashPledge);
                    BigDecimal total = reference.get();
                    if (total.add(changeCashPledge).compareTo(balanceCashPledge)<0){
                        map.put("refundPrice",changeCashPledge);
                    }else if (total.add(changeCashPledge).compareTo(balanceCashPledge)==0){
                        map.put("refundPrice",changeCashPledge);
                        atomicBoolean.set(false);
                    }else if (total.add(changeCashPledge).compareTo(balanceCashPledge)>0){
                        map.put("refundPrice",balanceCashPledge.subtract(total));
                        atomicBoolean.set(false);
                    }
                    refundList.add(map);
                }
            });
        log.info("押金退款订单->{}",JSONObject.toJSONString(refundList));
        refundList.forEach(refund->{
            WeixinJsRefundOrderReq req = new WeixinJsRefundOrderReq();
            String refundNum = OrderNumUtil.createUniqueOrderNum();
            req.setTransaction_id( (String) refund.get("payId"));
            req.setOut_trade_no( (String) refund.get("orderNum"));
            req.setOut_refund_no(refundNum);
            req.setReason("押金退款");
            RefundAmount refundAmount = new RefundAmount();
            BigDecimal a = (BigDecimal)refund.get("refundPrice");
            BigDecimal b = (BigDecimal)refund.get("total");
            long refundPrice = MoneyUtil.yuan2fen(a.doubleValue());
            long total = MoneyUtil.yuan2fen(b.doubleValue());
//            refundAmount.setRefund(refundPrice);
//            refundAmount.setTotal(total);
            refundAmount.setRefund(1L);
            refundAmount.setTotal(1L);
            req.setAmount(refundAmount);
            req.setNotify_url(WechatConfigurationConfig.wxUserWechatRefundNotifyUrl);
            try {
                WeixinRefundOrderVO weixinRefundOrderVO = new WeixinRefundOrderVO();
                WeixinJsRefundOrderRes refundOrder = weixinJsClient.createRefundOrder(req);
                log.info("微信退款返回值->{}",JSONObject.toJSONString(refundOrder));
                String status = refundOrder.getStatus();
                weixinRefundOrderVO.setStatus(status);
                if (!"SUCCESS".equals(status) && !"PROCESSING".equals(status)){
                    throw new BusinessException(ErrorCode.ERR_REFUND, "微信退款失败!");
                }
                WechatRefund wechatRefund = new WechatRefund();
                wechatRefund.setOrderType(DEPOSIT.getType());
                wechatRefund.setOrderNum(request.getOrderNum());
                wechatRefund.setRefundNum(refundNum);
                wechatRefund.setBalance(balanceCashPledge);
                wechatRefund.setUserId(userId);
                redisCacheService.hashPushHashMap(ORDER_REFUND.name(),refundNum,wechatRefund,EXPIRE_SECONDS, TimeUnit.SECONDS);
            } catch (Exception e) {
                e.printStackTrace();
                throw new BusinessException(ErrorCode.ERR_REFUND, "微信退款失败!");
            }
        });
        return true;
    }

    /**
     * 组装下单支付参数
     *
     * @param res
     * @param prepayId
     * @throws UnsupportedEncodingException
     * @throws NoSuchAlgorithmException
     * @throws SignatureException
     * @throws InvalidKeyException
     */
    private void buildPayRes(UserPurchaseRes res, String prepayId) throws UnsupportedEncodingException, NoSuchAlgorithmException, SignatureException, InvalidKeyException {
        res.setAppId(WechatConfigurationConfig.wxUserWechatAppid);
        res.setTimeStamp(System.currentTimeMillis() / 1000 + "");
        res.setNonceStr(UUID.randomUUID().toString().replaceAll("-", "").toUpperCase());
        res.setSignType("RSA");
        res.setDetailExtend("prepay_id=" + prepayId);
        // 签名，使用字段appId、timeStamp、nonceStr、package计算得出的签名值
        String preSign = res.getAppId() + "\n" + res.getTimeStamp() + "\n" + res.getNonceStr() + "\n" + res.getDetailExtend() + "\n";
        PrivateKey privateKey = PemUtil.loadPrivateKey(new ByteArrayInputStream(WechatConfigurationConfig.wxUserWechatPayApiClientKey.getBytes("utf-8")));
        String paySign = WeixinUtil.sign(preSign.getBytes(StandardCharsets.UTF_8), privateKey);
        res.setPaySign(paySign);
    }
}
